#include <string>
#include <assert.h>
#include "curveload.h"
#include "3dschunks.h"
#include "logger.h"

using namespace goatgfx;

#ifndef _MSC_VER
#include <stdint.h>
#else
typedef __int8 int8_t;
typedef __int16 int16_t;
typedef __int32 int32_t;
typedef unsigned __int8 uint8_t;
typedef unsigned __int16 uint16_t;
typedef unsigned __int32 uint32_t;
#endif

static uint32_t read_counter;

struct ChunkHeader {
	ChunkID id;
	uint32_t size;
};

#define HEADER_SIZE		6

// local function prototypes
static uint8_t read_byte(FILE *file);
static uint16_t read_int16(FILE *file);
static uint32_t read_int32(FILE *file);
static float read_float(FILE *file);
static Vector3 read_vector(FILE *file, bool flip_yz = false);
static std::string read_string(FILE *file);
static ChunkHeader read_chunk_header(FILE *file);
static void skip_chunk(FILE *file, const ChunkHeader &chunk);
static Curve *read_curve(FILE *file, const ChunkHeader &ch);

static bool eof;

bool load_curves(Scene *scn, const char *fname)
{
	FILE *file = fopen(fname, "rb");
	if(!file) {
		error_log("failed to open curves scene file: %s\n", fname);
		return false;
	}
	eof = false;

	ChunkHeader chunk;

	chunk = read_chunk_header(file);
	if(chunk.id != Chunk_3DSMain) {
		fclose(file);
		return false;
	}

	while(!eof) {
		chunk = read_chunk_header(file);

		Curve *curve;

		switch(chunk.id) {
		case Chunk_Main_3DEditor:
			break;	// dont skip

		case Chunk_Edit_Object:
			if((curve = read_curve(file, chunk))) {
				scn->curves.push_back(curve);
			}
			break;

		default:
			skip_chunk(file, chunk);
		}
	}

	fclose(file);
    return true;
}

static uint8_t read_byte(FILE *fp) {
	uint8_t v;
	if(fread(&v, 1, 1, fp) <= 0) {
		eof = true;
		return 0;
	}
	read_counter++;
	return v;
}

static uint16_t read_int16(FILE *fp) {
	uint16_t v;
	if(fread(&v, 2, 1, fp) <= 0) {
		eof = true;
		return 0;
	}
	read_counter += 2;
	return v;
}

static uint32_t read_int32(FILE *fp) {
	uint32_t v;
	if(fread(&v, 4, 1, fp) <= 0) {
		eof = true;
		return 0;
	}
	read_counter += 4;
	return v;
}

static float read_float(FILE *fp)
{
	int32_t tmp = read_int32(fp);
	return *((float*)&tmp);
}

static Vector3 read_vector(FILE *file, bool flip_yz)
{
	Vector3 vector;
	vector.x = read_float(file);
	if(!flip_yz) vector.y = read_float(file);
	vector.z = read_float(file);
	if(flip_yz) vector.y = read_float(file);
	return vector;
}

static std::string read_string(FILE *file)
{
	std::string str;
	char c;
	while((c = (char)read_byte(file))) {
		str.push_back(c);
	}
	read_counter++;

	return str;
}

static ChunkHeader read_chunk_header(FILE *file)
{
	ChunkHeader chunk;
	chunk.id = (ChunkID)read_int16(file);
	chunk.size = read_int32(file);
	return chunk;
}

static void skip_chunk(FILE *file, const ChunkHeader &chunk)
{
	if(eof) return;
	fseek(file, chunk.size - HEADER_SIZE, SEEK_CUR);
	read_counter += chunk.size - HEADER_SIZE;
}

static Curve *read_curve(FILE *file, const ChunkHeader &ch)
{
	read_counter = HEADER_SIZE;	// reset the global read counter

	std::string name = read_string(file);

	ChunkHeader chunk;
	chunk = read_chunk_header(file);
	if(chunk.id == Chunk_Obj_TriMesh) {
		// object is a trimesh... load it
		Vector3 *varray = 0;
		uint32_t vertex_count = 0;

		uint32_t obj_chunk_size = ch.size;

		while(read_counter < obj_chunk_size) {	// make sure we only read subchunks of this object chunk
			chunk = read_chunk_header(file);

            switch(chunk.id) {
			case Chunk_TriMesh_VertexList:
				vertex_count = (uint32_t)read_int16(file);
				varray = new Vector3[vertex_count];

				for(uint32_t i=0; i<vertex_count; i++) {
					varray[i] = read_vector(file);
				}

				break;

			case Chunk_TriMesh_FaceDesc:
				// it is a real object not a curve since it has triangles
				delete [] varray;
				varray = 0;
				break;

			default:
				skip_chunk(file, chunk);
			}
		}

		if(varray) {
			Curve *curve = new Curve;
			curve->set_name(name.c_str());
			for(uint32_t i=0; i<vertex_count; i++) {
				curve->add_point(varray[i]);
			}
			return curve;
		}
	}

	return 0;
}
